package chain

import (
	"awesomeProject1/pkg/chain"
	"awesomeProject1/pkg/e"
	"awesomeProject1/pkg/uuid"
	"context"
	"encoding/json"
	"errors"
	"fmt"
	"github.com/simplechain-org/client/log"
	"github.com/simplechain-org/go-simplechain/accounts/keystore"
	"github.com/simplechain-org/go-simplechain/core/types"
	"github.com/simplechain-org/go-simplechain/crypto"
	"github.com/simplechain-org/go-simplechain/p2p"
	"math/big"
	"sync"
)

type Service interface {
	SendTransaction(t, k, v string) (string, int)
	TransactionValidate(txHash string) (status int, code int)
	UpChain(v string) (string, int)
	UpChainByConsumer(data []byte) (string, error)
	UpdatePendingNonce()
	GetTransactionReceipt(txId string) (*types.Receipt, error)
	DeployContract(sol string) (txHash, contractAddr string, err error)
	GetChainClient() *chain.Client

	CompilerContract(solc, source string) (map[string]string, error)

	GetNodeInfo() (*p2p.NodeInfo, error)

	GetAccounts() ([]string, error)

	GetAllNodes(roleType int, from string) ([]string, error)

	PeerCount() (int64, error)

	BlockNumber() (uint64, error)

	BlockTime() (uint64, error)

	GetBlockByBlockNumOrHash(input string) (*chain.Block, error)

	GetHeaderByNumber(number string) (*types.Header, error)

	GetTransactionByHash(txId string) (*chain.Transaction, error)

	GetNodesOnChainOnline() ([]*chain.Node, int, error)

	UpdateNodeName(ctx context.Context, nodeId string, nodeName string, from string) (string, error)

	Stop()

	GetSigners() ([]string, error)

	IsNodeOnline(nodeId string) bool

	NodeId() (*chain.SimpleNodeInfo, error)

	GetFirstAccount() (string, error)
}

func (s *service) GetSigners() ([]string, error) {
	return s.chainClient.GetSigners()
}

type service struct {
	chainClient        *chain.Client
	chainId            *big.Int
	gasLimit           uint64
	gasPrice           *big.Int
	key                *keystore.Key
	mu                 sync.Mutex
	nonce              uint64
	isStop             uint32
	stop               chan struct{}
	blockNumber        uint64
	blockTime          uint64
	Id                 string
	isSubscribeNewHead bool
}

func (s *service) GetAccounts() ([]string, error) {
	return s.chainClient.GetAccounts()
}

func New(config *chain.Config, gasLimit uint64, gasPrice *big.Int, key *keystore.Key, isSubscribeNewHead bool) (Service, error) {
	client, err := chain.NewClient(config)
	if err != nil {
		return nil, err
	}
	chainId, err := client.ChainId()
	if err != nil {
		return nil, err
	}
	from := crypto.PubkeyToAddress(key.PrivateKey.PublicKey)

	nonce, err := client.GetNonce(from)
	if err != nil {
		return nil, err
	}
	blockNumber, err := client.BlockNumber()
	if err != nil {
		return nil, err
	}
	s := &service{
		chainClient:        client,
		gasLimit:           gasLimit,
		gasPrice:           gasPrice,
		chainId:            chainId,
		key:                key,
		nonce:              nonce,
		blockNumber:        blockNumber,
		Id:                 uuid.GetUUID(),
		isSubscribeNewHead: isSubscribeNewHead,
		isStop:             0,
		stop:               make(chan struct{}),
	}
	if isSubscribeNewHead {
		s.subscribeNewHead()
	}
	return s, nil
}

type HashType struct {
	Key   string `json:"key"`
	Value string `json:"value"`
}

func (s *service) SendTransaction(t, k, v string) (string, int) {
	data, err := json.Marshal(HashType{Key: k, Value: v})
	if err != nil {
		log.Error(err.Error())
		return "", e.InvalidParams
	}
	var prefix string
	switch t {
	case "hash":
		prefix = e.HashPrefix
	default:
		return "", e.InvalidParams
	}
	s.mu.Lock()
	defer s.mu.Unlock()
	txHash, err := s.chainClient.SendTransactionData(s.nonce, s.key.Address.String(), s.gasLimit, s.gasPrice, append([]byte(prefix), data...), s.key.PrivateKey, s.chainId)
	if err != nil {
		s.UpdatePendingNonce()
		log.Error(fmt.Sprintf("send transaction failed: %v", err))
		return "", e.ErrorSendTransactionFailed
	}
	s.nonce++
	return *txHash, e.Ok
}

func (s *service) TransactionValidate(txHash string) (status int, code int) {
	resp, err := s.chainClient.GetTransactionReceipt(txHash)
	if err != nil {
		log.Error(fmt.Sprintf("get transaction receipt failed: %v", err))
		return 0, e.ErrorGetTransactionReceiptFailed
	}
	if resp == nil {
		return e.TxPending, e.Ok
	}
	return int(resp.Status), e.Ok
}

var ErrNonceTooLow = errors.New("nonce too low")

func (s *service) UpChain(v string) (string, int) {
	s.mu.Lock()
	defer s.mu.Unlock()
	txHash, err := s.chainClient.SendTransactionData(s.nonce, s.key.Address.String(), s.gasLimit, s.gasPrice, []byte(v), s.key.PrivateKey, s.chainId)
	if err != nil {
		if err.Error() == ErrNonceTooLow.Error() { // nonce is too low
			log.Error(fmt.Sprintf("send transaction failed: nonce too low so update new nonce(%v) in cache", s.nonce))
		} else {
			log.Error(fmt.Sprintf("send transaction failed: %v", err))
		}
		s.UpdatePendingNonce()
		return "", e.ErrorSendTransactionFailed
	}
	s.nonce++
	return *txHash, e.Ok
}

func (s *service) UpChainByConsumer(data []byte) (string, error) {
	s.mu.Lock()
	defer s.mu.Unlock()
	txHash, err := s.chainClient.SendTransactionUpChain(s.key, s.nonce, s.gasLimit, s.gasPrice, data)
	if err != nil {
		s.UpdatePendingNonce()
		return "", errors.New(fmt.Sprintf("send transaction failed: (newNonce=%d txHash=%s) %s", s.nonce, txHash, err.Error()))
	}
	s.nonce++
	return txHash, nil
}

// UpdatePendingNonce 在locker下调用
func (s *service) UpdatePendingNonce() {
	// 重置nonce
	pendingNonceResult, err := s.chainClient.GetNonce(s.key.Address)
	if err == nil {
		s.nonce = pendingNonceResult
	} else {
		log.Error("UpdatePendingNonce", "err", err.Error())
	}
}

func (s *service) GetChainClient() *chain.Client {
	return s.chainClient
}

func GetKey(filename, auth string) (*keystore.Key, error) {
	return chain.GetKey(filename, auth)
}
